﻿// License: The MIT License (MIT) Copyright (c) 2011..2012 Barend Gehrels

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Windows.Threading;

using Stormy;

namespace GrooveSharp
{
    class FolderWatcher
    {
        private DispatcherTimer m_updateFileListTimer;
        private FileSystemWatcher m_grooveMp3Watcher = null;
        private IList<string> m_newlyCreatedFiles = new List<string>();

        private GroovePath m_path;

        public FolderWatcher(GroovePath path)
        {
            m_path = path;

            try
            {
                m_updateFileListTimer = new DispatcherTimer();
                m_updateFileListTimer.Interval = TimeSpan.FromSeconds(1.0);
                m_updateFileListTimer.Tick += new EventHandler(updateFileListTimer_Tick);

                StartWatching();
                App.Log.InfoFormat("Started watching {0} for changes", path.PathName);
            }
            catch (Exception e)
            {
                App.Log.ErrorFormat("Exception in watching folder {0}: {1}", path.PathName, e.Message);

                m_grooveMp3Watcher.Dispose();
                m_grooveMp3Watcher = null;
            }
        }

        private void StartWatching()
        {
            // TODO: add a second watcher for M4A. Note that (according to MSDN) multiple filters are not possible. So we have to create to
            m_grooveMp3Watcher = new FileSystemWatcher();
            m_grooveMp3Watcher.Path = m_path.PathName;
            m_grooveMp3Watcher.Filter = "*.mp3";
            m_grooveMp3Watcher.IncludeSubdirectories = true;
            m_grooveMp3Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;

            m_grooveMp3Watcher.Created += new FileSystemEventHandler(Mp3FileCreation);
            m_grooveMp3Watcher.Renamed += new RenamedEventHandler(Mp3FileRenaming);
            m_grooveMp3Watcher.Deleted += new FileSystemEventHandler(Mp3FileDeletion);

            m_grooveMp3Watcher.EnableRaisingEvents = true;
        }

        private bool ReviveDeletedFileAtCreation(string fullPath)
        {
            long fileSize = new FileInfo(fullPath).Length;
            string fileName, folderName;
            FolderScanner.Split(m_grooveMp3Watcher.Path, fullPath, out folderName, out fileName);

            // TODO: move this SQL to somewhere
            int trackId = Db.SelectFirstOrDefault<int>
                (
                    "select track_id from tracks where file_name = @name and file_size = @size and is_deleted = 1", 
                    new List<StormyParameter>() 
                            { 
                                Db.CreateParameter(DbType.String, "@name", fileName),
                                Db.CreateParameter(DbType.Int32, "@size", fileSize)
                            }
                );


            if (trackId > 0)
            {

                // Found! We just did found a file with same specifics, so revive this row.
                Db.Connection().Execute
                    (
                        "update tracks set folder_name=@folder, is_deleted=0 where track_id=@id",
                        new List<StormyParameter>() 
                                { 
                                    Db.CreateParameter(DbType.Int32, "@id", trackId),
                                    Db.CreateParameter(DbType.String, "@folder", folderName)
                                }
                    );

                App.Log.Info("REVIVED: " + folderName + " / " + fullPath);

            }

            return trackId > 0;
        }

        private void updateFileListTimer_Tick(object sender, EventArgs e)
        {
            lock(this)
            {
                m_updateFileListTimer.Stop();

                FolderScanner scanner = new FolderScanner();
                foreach (string file in m_newlyCreatedFiles)
                {
                    // Try to find an existing row of which is_deleted=true, then it is plausible to reuse that one...
                    if (!ReviveDeletedFileAtCreation(file))
                    {
                        try
                        {
                            scanner.AddFile(m_path, file);
                            App.Log.Info("Really created: " + file);
                        }
                        catch
                        {
                            // It might fail, e.g. if it was added and removed immediately. Or if it is still in use. 
                            // Don't report but try next file. No harm done, will be catched/refreshed later on.
                        }
                    }
                }
                App.Log.Info("Created/revived: " + m_newlyCreatedFiles.Count.ToString() + " files processed");
                m_newlyCreatedFiles.Clear();
            }
        }


        private void Mp3FileCreation(object source, FileSystemEventArgs e)
        {
            App.Log.Info("Created/revived: " + e.FullPath + " " + e.ChangeType);

            // Add it to a queue (do not process it immediately to avoid "it is being used by another process")
            lock(this)
            {
                m_newlyCreatedFiles.Add(e.FullPath);

                // Restart the timer
                m_updateFileListTimer.Stop();
                m_updateFileListTimer.Start();
            }
        }

        private void Mp3FileDeletion(object source, FileSystemEventArgs e)
        {
            // If a file is deleted... it should stay in the database (for hits) but cannot be played anymore.
            // Besides that, it can be moved from folder -> deletion followed by creation, this is not catched as ONE event by the filesystemwatcher...
            // If so, will be revived later on (see above)
            // So set the deletion-flag on, for now (one second) or for longer time.

            string fileName, folderName;
            FolderScanner.Split(m_grooveMp3Watcher.Path, e.FullPath, out folderName, out fileName);

            App.Log.Info("Deleted File: " + e.FullPath + " " + e.ChangeType);

            Db.Connection().Execute
                (
                    "update tracks set is_deleted=1 where folder_name=@folder and file_name=@file",
                    new List<StormyParameter>() 
                    { 
                        Db.CreateParameter(DbType.String, "@folder", folderName),
                        Db.CreateParameter(DbType.String, "@file", fileName),
                    }
                );
        }

        private void Mp3FileRenaming(object source, RenamedEventArgs e)
        {
            string oldFileName, oldFolderName;
            string newFileName, newFolderName;
            FolderScanner.Split(m_grooveMp3Watcher.Path, e.OldFullPath, out oldFolderName, out oldFileName);
            FolderScanner.Split(m_grooveMp3Watcher.Path, e.FullPath, out newFolderName, out newFileName);

            App.Log.InfoFormat("File: {0} {1} renamed to {2} {3}", oldFolderName, oldFileName, newFolderName, newFileName);

            // TODO: change to update mapper
            // Try to update it. It might be not successful, anyway on non-success we quit, no harm done, refresh later.
            Db.Connection().Execute
                (
                    "update tracks set folder_name=@newfolder,file_name=@newfile where folder_name=@oldfolder and file_name=@oldfile",
                    new List<StormyParameter>() 
                    { 
                        Db.CreateParameter(DbType.String, "@newfolder", newFolderName),
                        Db.CreateParameter(DbType.String, "@newfile", newFileName),
                        Db.CreateParameter(DbType.String, "@oldfolder", oldFolderName),
                        Db.CreateParameter(DbType.String, "@oldfile", oldFileName)
                    }
                );
        }
    }
}
